use crate::controller::error::{Error, ObjectHasBeenReferenceError};
use crate::controller::event_handler::endpoint::EventHandler;
use crate::{dto, mq, service::ServiceSet};
use std::sync::Arc;
use wanmesh_common::dto::ExistsResponseData;

pub struct EndpointController {
    service_set: Arc<ServiceSet>,
    event_handler: EventHandler,
}

impl EndpointController {
    pub fn new(service_set: Arc<ServiceSet>, mq_client_set: Arc<mq::client::ClientSet>) -> Self {
        Self {
            service_set,
            event_handler: EventHandler::new(mq_client_set),
        }
    }
    pub async fn add(
        &self,
        request: &dto::endpoint::AddRequest,
    ) -> Result<dto::core::endpoint::AddResponseData, Error> {
        let data = self.service_set.endpoint_manager.add(request).await?;

        self.event_handler
            .spawn_handle_setup_event(request.inner.node_id.as_str(), dto::event::endpoint::SetupEventPayload {
                endpoint_id: data.endpoint_id.clone(),
            });

        Ok(data)
    }

    pub async fn list(
        &self,
        request: &dto::endpoint::ListRequest,
    ) -> Result<dto::endpoint::ListResponseData, Error> {
        let total_count = self
            .service_set
            .endpoint_manager
            .count(&request.filter)
            .await?;
        if total_count == 0 {
            return Ok(dto::endpoint::ListResponseData {
                total_count,
                list: Vec::new(),
            });
        }

        let list = self
            .service_set
            .endpoint_manager
            .list(&request.filter, request.offset, request.limit)
            .await?;
        Ok(dto::endpoint::ListResponseData { total_count, list })
    }

    pub async fn list_info(
        &self,
        request: &dto::endpoint::ListRequest,
    ) -> Result<dto::endpoint::ListInfoResponseData, Error> {
        let total_count = self
            .service_set
            .endpoint_manager
            .count(&request.filter)
            .await?;
        if total_count == 0 {
            return Ok(dto::endpoint::ListInfoResponseData {
                total_count,
                list: Vec::new(),
            });
        }

        let list = self
            .service_set
            .endpoint_manager
            .list_info(&request.filter, request.offset, request.limit)
            .await?;
        Ok(dto::endpoint::ListInfoResponseData { total_count, list })
    }

    pub async fn get(
        &self,
        request: &dto::endpoint::GetRequest,
    ) -> Result<dto::core::endpoint::EndpointItem, Error> {
        let data = self
            .service_set
            .endpoint_manager
            .get(request.endpoint_id.as_str())
            .await?;
        Ok(data)
    }

    pub async fn try_get(
        &self,
        request: &dto::endpoint::GetRequest,
    ) -> Result<Option<dto::core::endpoint::EndpointItem>, Error> {
        let data = self
            .service_set
            .endpoint_manager
            .try_get(request.endpoint_id.as_str())
            .await?;

        Ok(data)
    }

    pub async fn get_config(
        &self,
        request: &dto::endpoint::GetRequest,
    ) -> Result<dto::endpoint::EndpointInfo, Error> {
        let data = self
            .service_set
            .endpoint_manager
            .get_info(request.endpoint_id.as_str())
            .await?;

        Ok(data)
    }

    pub async fn try_get_config(
        &self,
        request: &dto::endpoint::GetRequest,
    ) -> Result<Option<dto::endpoint::EndpointInfo>, Error> {
        let data = self
            .service_set
            .endpoint_manager
            .try_get_info(request.endpoint_id.as_str())
            .await?;

        Ok(data)
    }

    pub async fn update(&self, request: &dto::endpoint::UpdateRequest) -> Result<(), Error> {
        if request.params == Default::default() {
            return Ok(());
        }

        let endpoint = self
            .service_set
            .endpoint_manager
            .get(request.endpoint_id.as_str())
            .await?;

        self.service_set
            .endpoint_manager
            .update(request.endpoint_id.as_str(), &request.params)
            .await?;

        if request.params.ipv4 != Default::default() || request.params.ipv6 != Default::default() {
            self.event_handler
                .spawn_handle_setup_event(endpoint.inner.node_id.as_str(), dto::event::endpoint::SetupEventPayload {
                    endpoint_id: request.endpoint_id.clone(),
                });
        }

        Ok(())
    }

    pub async fn delete(&self, request: &dto::endpoint::DeleteRequest) -> Result<(), Error> {
        self.check_has_been_referenced(request.endpoint_id.as_str())
            .await?;

        let endpoint = self
            .service_set
            .endpoint_manager
            .get(request.endpoint_id.as_str())
            .await?;

        self.service_set
            .endpoint_manager
            .delete(request.endpoint_id.as_str())
            .await?;

        self.event_handler
            .spawn_handle_delete_event(endpoint.inner.node_id.as_str(), dto::event::endpoint::DeleteEventPayload {
                endpoint_id: request.endpoint_id.clone(),
                endpoint_type: endpoint.inner.endpoint_type,
            });

        Ok(())
    }

    pub async fn exists(
        &self,
        request: &dto::endpoint::ExistsRequest,
    ) -> Result<ExistsResponseData, Error> {
        let exists = self
            .service_set
            .endpoint_manager
            .exists(&request.filter)
            .await?;

        Ok(ExistsResponseData { exists })
    }

    async fn check_has_been_referenced(&self, endpoint_id: &str) -> Result<(), Error> {
        if let Some(v) = self
            .service_set
            .endpoint_peer_manager
            .list(
                &dto::core::endpoint_peer::Filter {
                    endpoint_id: Some(endpoint_id.to_string()),
                    ..Default::default()
                },
                0,
                1,
            )
            .await?
            .get(0)
        {
            return Err(
                ObjectHasBeenReferenceError::endpoint_peer(v.endpoint_peer_id.as_str()).into(),
            );
        }

        if let Some(v) = self
            .service_set
            .endpoint_peer_manager
            .list(
                &dto::core::endpoint_peer::Filter {
                    peer_endpoint_id: Some(endpoint_id.to_string()),
                    ..Default::default()
                },
                0,
                1,
            )
            .await?
            .get(0)
        {
            return Err(
                ObjectHasBeenReferenceError::endpoint_peer(v.endpoint_peer_id.as_str()).into(),
            );
        }

        if let Some(v) = self
            .service_set
            .traffic_steering_rule
            .list(
                &dto::core::traffic_steering_rule::Filter {
                    endpoint_id: Some(endpoint_id.to_string()),
                    ..Default::default()
                },
                0,
                1,
            )
            .await?
            .get(0)
        {
            return Err(ObjectHasBeenReferenceError::traffic_steering_rule(
                v.traffic_steering_rule_id.as_str(),
            )
                .into());
        }

        Ok(())
    }
}
